/**
 * Copyright 2019 吉鼎科技.
 *
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.interceptor;

import cn.easyplatform.EasyPlatformWithLabelKeyException;
import cn.easyplatform.InvalidUserException;
import cn.easyplatform.ServicePausedException;
import cn.easyplatform.UserOfflineException;
import cn.easyplatform.cfg.*;
import cn.easyplatform.contexts.ExecuteContext;
import cn.easyplatform.contexts.RecordContext;
import cn.easyplatform.contexts.WorkflowContext;
import cn.easyplatform.dao.*;
import cn.easyplatform.dos.EnvDo;
import cn.easyplatform.dos.FieldDo;
import cn.easyplatform.dos.LogDo;
import cn.easyplatform.dos.UserDo;
import cn.easyplatform.entities.BaseEntity;
import cn.easyplatform.entities.beans.project.PortletBean;
import cn.easyplatform.lang.Files;
import cn.easyplatform.lang.Nums;
import cn.easyplatform.lang.Strings;
import cn.easyplatform.log.LogManager;
import cn.easyplatform.services.IProjectService;
import cn.easyplatform.services.system.DefaultIdCallback;
import cn.easyplatform.spi.listener.event.ConsoleEvent;
import cn.easyplatform.spi.listener.event.Event;
import cn.easyplatform.support.scripting.BreakPoint;
import cn.easyplatform.support.sql.SqlParser;
import cn.easyplatform.type.*;
import cn.easyplatform.util.RuntimeUtils;
import cn.easyplatform.utils.Assert;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.Serializable;
import java.util.*;

/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public class CommandContext {

    public final static String USER_KEY = "${PLATFORM.UESR}";

    public final static String USER_ENV = "${USER.ENV}";

    private final static Logger _log = LoggerFactory.getLogger(CommandContext.class);

    private EngineConfiguration _engineConfiguration;

    private Session _session;

    private EnvDo _env;

    private UserDo _user;

    private WorkflowContext _ctx;

    private TransactionContext _tx;

    private IProjectService _projectService;

    //仅提供测试
    public CommandContext() {
    }

    /**
     * @param engineConfiguration 背景服务
     */
    public CommandContext(EngineConfiguration engineConfiguration) {
        this._engineConfiguration = engineConfiguration;
        Subject subject = SecurityUtils.getSubject();
        _session = subject.getSession();
    }

    /**
     * @param engineConfiguration
     * @param cmd
     */
    public CommandContext(EngineConfiguration engineConfiguration, Command cmd) {
        IRequestMessage<?> req = ((AbstractCommand<?>) cmd).req;
        this._engineConfiguration = engineConfiguration;
        // 初始化执行环境
        Subject subject = null;
        if (req.getSessionId() != null) {
            /*if (cmd.getName() != null && cmd.getName().startsWith("apis.")) {//通过jwt访问
                try {
                    String id = Jwts.parser().setSigningKey(engineConfiguration.getPublicKey()).parseClaimsJws((String) req.getSessionId()).getBody().getId();
                    subject = new Subject.Builder().sessionId(id).buildSubject();
                } catch (Exception e) {
                    if (_log.isInfoEnabled())
                        _log.info("Jwt error", e);
                    throw new InvalidUserException();
                }
                Object token = subject.getSession().getAttribute("jwt-token");
                if (token != null && !req.getSessionId().equals(token)) {
                    if (_log.isInfoEnabled())
                        _log.info("Jwt token expire");
                    throw new InvalidUserException();
                }
            } else*/
            subject = new Subject.Builder().sessionId(req.getSessionId()).buildSubject();
            ThreadContext.bind(subject);
            _session = subject.getSession();
            _session.touch();
        } else {
            subject = SecurityUtils.getSubject();
            _session = subject.getSession();
            req.setSessionId(_session.getId());
        }
        _user = (UserDo) _session.getAttribute(USER_KEY);
        _env = (EnvDo) _session.getAttribute(USER_ENV);
        if (_env != null) {
            _projectService = (IProjectService) engineConfiguration
                    .getService(_env.getId());
            LogManager.beginRequest(_env.getId(), _user);
        }
        if (req.getId() != null)
            _ctx = (WorkflowContext) _session.getAttribute(req.getId());
    }

    /**
     * 检查请求是否有效
     *
     * @param cmd
     */
    void validate(Command cmd) {
        // 检查用户是否有效
        if (_user != null && _projectService != null && _user.getType() < UserType.TYPE_OAUTH) {
            _projectService.validate(_user, false);// 检查项目的状态
            if (_env.getDeviceType() != DeviceType.JOB
                    && _env.getDeviceType() != DeviceType.API) {
                if (_user.getType() != UserType.TYPE_SHARE && _user.getType() != UserType.TYPE_ADMIN) {
                    int type = _projectService.getSessionManager().checkUser(_user);
                    if (type != UserDo.ONLINE_SELF) {
                        if (type == UserDo.ONLINE_OTHER
                                && _user.getType() == UserType.TYPE_ONLY
                                && ("login.Confirm".equals(cmd.getName()))) {
                        } else {
                            ThreadContext.getSubject().logout();
                            if (type == UserDo.ONLINE_NONE)
                                throw new InvalidUserException();
                            else
                                throw new UserOfflineException();
                        }
                    } else if (_user.getState() == StateType.STOP) {
                        ThreadContext.getSubject().logout();
                        throw new InvalidUserException();
                    } else if (_user.getState() == StateType.PAUSE)
                        throw new ServicePausedException("easyplatform.user.paused");
                } else if (_user.getState() == StateType.PAUSE)
                    throw new ServicePausedException("easyplatform.user.paused");
                else if (_user.getState() == StateType.STOP) {
                    ThreadContext.getSubject().logout();
                    throw new InvalidUserException();
                }
                _user.setLastAccessTime(new Date());
                _session.setAttribute(CommandContext.USER_KEY, _user);
            }
        }
        if (cmd.getName() != null) {
            if (!cmd.getName().startsWith("login.") && (_user == null || _env == null)) {
                ThreadContext.getSubject().logout();
                throw new InvalidUserException();
            }
            if (_ctx != null && cmd.getName().startsWith("task."))
                _ctx.setParameter("759", cmd.getName().substring(5));
        }
    }

    /**
     * 开始事务
     */
    public void beginTx() {
        _tx = _projectService.getTransactionContext(this);
        if (_log.isDebugEnabled())
            _log.debug("begin transaction for {}", _ctx == null ? "" : _ctx.getId());
    }

    /**
     * 提交事务
     */
    public void commitTx() {
        Assert.notNull(_tx,
                "this transactionContext is required; it must not be null");
        _tx.commit();
        if (_log.isDebugEnabled())
            _log.debug("commit transaction for {}", _ctx == null ? "" : _ctx.getId());
    }

    /**
     * 回滚事务
     */
    public void rollbackTx() {
        if (_tx != null)
            _tx.rollback();
        if (_log.isDebugEnabled())
            _log.debug("rollback transaction for {}", _ctx == null ? "" : _ctx.getId());
    }

    /**
     * 关闭事务中的连接
     */
    public void closeTx() {
        if (_tx != null) {
            _tx.close();
            if (_log.isDebugEnabled())
                _log.debug("close transaction for {}", _ctx == null ? "" : _ctx.getId());
        }
    }

    /**
     * 根据路径返回项目服务
     *
     * @param appContext
     * @return
     */
    public IProjectService getProjectService(String appContext) {
        Collection<IProjectService> psl = _engineConfiguration.getProjectServices();
        if (Strings.isBlank(appContext)) {
            String appId = _engineConfiguration.getPlatformAttribute("app.default");
            if (!Strings.isBlank(appId)) {
                IProjectService ps = _engineConfiguration.getProjectService(appId);
                if (ps != null)
                    return ps;
            }
            return psl.iterator().next();
        }
        for (IProjectService ps : psl) {
            if (ps.getEntity().getAppContext().equals(appContext))
                return ps;
        }
        //如果不存在，用Id查找
        return _engineConfiguration.getProjectService(appContext);
    }

    /**
     * 获取当前工作的项目
     *
     * @return
     */
    public IProjectService getProjectService() {
        return _projectService;
    }


    /**
     * @param acc
     * @return
     */
    public Integer getAcc(String acc) {
        return _projectService.getBizService().getFractionDigits(acc);
    }

    /**
     * 获取提示代码信息
     *
     * @param code
     * @param rc
     * @return
     */
    public String getMessage(String code, RecordContext rc) {
        return _projectService.getResourceHandler().getMessage(code, rc);
    }

    /**
     * 获取i18N资源
     *
     * @return
     */
    public Map<String, String> getLabels() {
        return _projectService.getResourceHandler().getLabels(_user.getLocale());
    }

    /**
     * @return
     */
    public String getLabel(String code) {
        if (_user == null)
            return _projectService.getResourceHandler().getLabel(_projectService.getLocale().toString(), code);
        return _projectService.getResourceHandler().getLabel(_user.getLocale(),
                code);
    }

    /**
     * @return
     */
    public String getLabel(String code, String locale) {
        if (Strings.isBlank(locale))
            locale = _projectService.getLocale().toString();
        return _projectService.getResourceHandler().getLabel(locale,
                code);
    }

    /**
     * 根据实体id获取实体参数对象
     *
     * @return
     */
    public <T extends BaseEntity> T getEntity(String entityId) {
        if (Strings.isBlank(entityId))
            return null;
        return _projectService.getEntityHandler().getEntity(entityId);
    }

    /**
     * @return
     */
    public EntityDao getEntityDao() {
        return _engineConfiguration.getEntityDao();
    }

    /**
     * 获取easyplatform的配置项
     *
     * @param name
     * @return
     */
    public String getPlatformConfigOption(String name) {
        return _engineConfiguration.getPlatformAttribute(name);
    }

    /**
     * 获取当前工作的验证数据接口
     *
     * @return
     */
    public IdentityDao getIdentityDao(boolean isUser) {
        EnvDo ed = getEnv();
        if (ed == null)
            throw new UnknownSessionException();
        return isUser ? _projectService.getIdentityDao() : DaoFactory.createIdentityDao(_projectService.getDataSource());
    }

    /**
     * 项目默认的数据接口
     *
     * @return
     */
    public BizDao getBizDao() {
        return _projectService.getBizDao();
    }

    /**
     * 项目引用其它数据库
     *
     * @return
     */
    public BizDao getBizDao(String resourceId) {
        return _projectService.getBizDao(resourceId);
    }

    /**
     * 发送调试信息给客户端
     *
     * @param obj
     */
    public void send(Object obj) {
        if (obj == null)
            return;
        if (_user.getDeviceType() == DeviceType.AJAX) {
            if (obj instanceof Event) {//日志和应用
                _projectService.publish(Constants.EASYPLATFORM_TOPIC, (Event) obj, _user.getId());
            } else if (_user.isDebugEnabled()) {//控制台
                WorkflowContext ctx = getWorkflowContext();
                String id = ctx == null ? "" : ctx.getParameterAsString("801");
                String name = ctx == null ? "" : ctx.getName();
                if (!(obj instanceof Serializable))
                    obj = obj.toString();
                _projectService.publish(Constants.EASYPLATFORM_TOPIC, new ConsoleEvent(id, name, obj), _user.getId());
            }
        }
    }

    /**
     * 推送消息
     *
     * @param topic
     * @param msg
     * @param toUsers
     */
    public void publish(String topic, Serializable msg, String... toUsers) {
        if (Strings.isBlank(topic))
            topic = Constants.EASYPLATFORM_TOPIC;
        _projectService.publish(topic, msg, toUsers);
    }

    /**
     * 发送消息
     *
     * @param topic
     * @param data
     * @param toUsers
     */
    public void sendMessage(String topic, Serializable data, String... toUsers) {
        _engineConfiguration.getApplicationListener().send(_projectService.getId(), topic, data, toUsers);
    }

    /**
     * 表id生成器，是指需要动态生成的功能
     *
     * @return
     */
    public IdGenerator getIdGenerator() {
        return _projectService.getIdGenerator();
    }

    /**
     * 当前功能对象
     *
     * @return
     */
    public WorkflowContext getWorkflowContext() {
        return _ctx;
    }

    /**
     * @param id
     * @return
     */
    public WorkflowContext getWorkflowContext(String id) {
        return (WorkflowContext) _session.getAttribute(id);
    }

    /**
     * 获取其它会话功能
     *
     * @param sessionId
     * @param id
     * @return
     */
    public WorkflowContext getWorkflowContext(Serializable sessionId, String id) {
        Subject subject = new Subject.Builder().sessionId(sessionId).buildSubject();
        return (WorkflowContext) subject.getSession().getAttribute(id);
    }

    /**
     * 获取指定会话
     *
     * @param userId
     * @return
     */
    public List<WorkflowContext> getWorkflowContexts(String userId) {
        UserDo user = _projectService.getSessionManager().getUser(userId);
        if (user == null)
            return Collections.emptyList();
        Subject subject = new Subject.Builder().sessionId(user.getSessionId()).buildSubject();
        List<WorkflowContext> ctxs = new ArrayList<WorkflowContext>();
        for (Object sid : subject.getSession().getAttributeKeys()) {
            if (sid.toString().startsWith(WorkflowContext.PREFIX))
                ctxs.add((WorkflowContext) subject.getSession().getAttribute(sid));
        }
        return ctxs;
    }

    /**
     * @return
     */
    public List<WorkflowContext> getWorkflowContexts() {
        List<WorkflowContext> ctxs = new ArrayList<WorkflowContext>();
        for (Object sid : _session.getAttributeKeys()) {
            if (sid.toString().startsWith(WorkflowContext.PREFIX))
                ctxs.add((WorkflowContext) _session.getAttribute(sid));
        }
        return ctxs;
    }

    /**
     * 设置当前的工作对象
     *
     * @param ctx
     */
    public void setWorkflowContext(WorkflowContext ctx) {
        this._ctx = ctx;
        _session.setAttribute(ctx.getId(), ctx);
    }

    /**
     * 移除当前的工作对象
     */
    public void removeWorkflowContext() {
        if (_ctx != null) {
            _session.removeAttribute(_ctx.getId());
            if (_user != null)
                getSync().unlock(_ctx.getId(), 0);
            if (_ctx.getParentId() != null) {
                WorkflowContext parent = (WorkflowContext) _session
                        .getAttribute(_ctx.getParentId());
                if (parent != null)
                    parent.removeChild(_ctx);
            }
            _ctx.removeAll(this);
            if (_log.isDebugEnabled())
                _log.debug("remove->{}", _ctx);
            _ctx = null;
        }
    }

    /**
     * 清除所有的功能信息
     */
    public void clear(Object[] excepts) {
        List<Object> tmp = new ArrayList<Object>();
        for (Object name : _session.getAttributeKeys()) {
            if (name instanceof String
                    && ((String) name).startsWith(WorkflowContext.PREFIX)) {
                if (excepts == null)
                    tmp.add(name);
                else if (!ArrayUtils.contains(excepts, name))
                    tmp.add(name);
            }
        }
        for (Object name : tmp) {
            Object wc = _session.removeAttribute(name);
            if (_log.isDebugEnabled())
                _log.debug("removeWorkflowContext->{}", wc);
            wc = null;
        }
        if (_projectService != null)
            _projectService.getRowLockProvider(this).clear();
    }

    /**
     * 登陆系统
     */
    public void login() {
        StringBuilder sb = new StringBuilder();
        sb.append(_projectService.getWorkspace()).append("/")
                .append(_user.getId());
        _user.setWorkspace(sb.toString());
        sb.setLength(0);
        sb.append(_engineConfiguration.getWorkspace()).append(
                _user.getWorkspace());
        _user.setWorkPath(sb.toString());
        Files.createDirIfNoExists(new File(sb.toString()));
        sb = null;
        if (!Strings.isBlank(_env.getPortlet())) {
            // 获取其它有关于portlet自定义变量
            PortletBean pb = _projectService.getPortlet(_env.getPortlet());
            if (!Strings.isBlank(pb.getOnInitQuery())) {
                Map<String, Object> systemVariables = new HashMap<String, Object>();
                RuntimeUtils.initWorkflow(systemVariables, null);
                systemVariables.put("710", _env.getPortlet());
                systemVariables.put("728", _env.getDeviceType().getName());
                RecordContext rc = new RecordContext(null, systemVariables,
                        null);
                SqlParser<FieldDo> parser = RuntimeUtils.createSqlParser(FieldDo.class);
                String query = parser.parse(pb.getOnInitQuery(), rc);
                FieldDo[] variables = _projectService.getBizDao().selectOne(
                        query, parser.getParams());
                if (variables != null) {
                    if (_user.getExtraInfo() == null)
                        _user.setExtraInfo(new HashMap<String, Object>(
                                variables.length));
                    for (FieldDo fd : variables) {
                        if (!fd.getName().toLowerCase().startsWith("v"))
                            throw new EasyPlatformWithLabelKeyException(
                                    "dao.user.variable.invalid",
                                    fd.getName());
                        int no = Nums.toInt(fd.getName().substring(1), 0);
                        if (no == 0)
                            throw new DaoException(
                                    "dao.user.variable.invalid",
                                    fd.getName());
                        _env.getVariables().put(String.valueOf(no),
                                fd.getValue());
                    }// for
                }// if
            }// if query
            if (!_env.getVariables().isEmpty()) {
                Map<String, Object> extraInfo = _user.getExtraInfo();
                if (extraInfo == null) {
                    extraInfo = new HashMap<String, Object>(_env
                            .getVariables().size());
                    _user.setExtraInfo(extraInfo);
                }
                extraInfo.putAll(_env.getVariables());
            }
        }// if portlet
        //if (_user.getDeviceType() != DeviceType.API)
        //    _projectService.getSessionManager().setUser(_user);
    }

    /**
     * 用户退出
     */
    public void logout() {
        SecurityUtils.getSubject().logout();
        if (_user != null && _projectService != null) {
            if (_user.getDeviceType() != DeviceType.API)
                // getIdentityDao(false).updateState(_user.getId(), null,
                // STATE_ACTIVATED);
                if (_user.getType() < UserType.TYPE_OAUTH && _user.getLogLevel() >= LogDo.LEVEL_USER)
                    getBizDao().log(
                            new LogDo(_user.getEventId(), _user.getId(), _user
                                    .getDeviceType().getName(), LogDo.TYPE_USER,
                                    "logout", _user.getIp()));
            //getSync().clear();
        }
    }

    /**
     * 设定用户的工作信息
     *
     * @param env
     */
    public void setupEnv(EnvDo env) {
        _session.setAttribute(USER_ENV, env);
        this._env = env;
        _projectService = (IProjectService) _engineConfiguration.getService(env.getId());
        _session.setTimeout(_projectService.getConfig().getSessionTimeout() * 1000);
    }

    /**
     * 获取用户的工作信息
     *
     * @return
     */
    public EnvDo getEnv() {
        return _env;
    }

    /**
     * 设置用户
     *
     * @param user
     */
    public void setUser(UserDo user) {
        if (user == null)
            _session.removeAttribute(USER_KEY);
        else {
            this._user = user;
            if (_env != null) {
                if (this._user.getDeviceType() == null)
                    this._user.setDeviceType(_env.getDeviceType());
                this._user.setLogLevel(_projectService.getConfig().getLogLevel());
                this._user.setIdCallback(new DefaultIdCallback());
            }
            this._user.setSessionId(_session.getId());
            _session.setAttribute(USER_KEY, this._user);
        }
    }

    /**
     * 设置会话属性
     *
     * @param key
     * @param value
     */
    public CommandContext set(String key, Object value) {
        if (value == null)
            _session.removeAttribute(key);
        else
            _session.setAttribute(key, value);
        return this;
    }

    /**
     * @param key
     * @return
     */
    public Object get(String key) {
        return _session.getAttribute(key);
    }

    /**
     * @param key
     * @return
     */
    public Object remove(String key) {
        return _session.removeAttribute(key);
    }

    /**
     * 获取当前用户对象
     *
     * @return
     */
    public UserDo getUser() {
        return _user;
    }

    /**
     * @param bp
     */
    public void setBreakPoint(BreakPoint bp) {
        _ctx.setBreakPoint(bp);
    }

    /**
     * @return
     */
    public BreakPoint getBreakPoint() {
        if (_ctx == null)
            return null;
        return _ctx.getBreakPoint();
    }

    public EngineConfiguration getEngineConfiguration() {
        return _engineConfiguration;
    }

    /**
     * @return
     */
    public BusinessDateHandler getBusinessDateHandler() {
        return _projectService.getBusinessDateHandler(this);
    }

    /**
     * 工作流引擎
     *
     * @return
     */
    public BpmEngineContext getBpmEngine() {
        return _projectService.getBpmEngine(this);
    }

    /**
     * 系统同步器
     *
     * @return
     */
    public RowLockProvider getSync() {
        return _projectService.getRowLockProvider(this);
    }

    /**
     * @param path
     * @return
     */
    public String getRealPath(String path) {
        if (path.startsWith("$")) {
            String name = path.substring(0, 4);
            path = path.replace(name, getPath(name.substring(1)));
        } else if (path.startsWith("/"))
            path = _projectService.getWorkspace() + path;
        else
            path = _projectService.getWorkspace() + "/" + path;
        return FilenameUtils.normalize(path);
    }

    /**
     * @param name
     * @return
     */
    private String getPath(String name) {
        if (name.equals("707"))
            return _projectService.getWorkPath();
        if (name.equals("708"))
            return _engineConfiguration.getWebWorkspacePath() + _projectService.getWorkspace();
        if (name.equals("729"))
            return _user.getWorkPath();
        if (name.equals("730"))
            return _engineConfiguration.getWebWorkspacePath() + _user.getWorkspace();
        throw new EasyPlatformWithLabelKeyException("vfs.dir.invalid", "$" + name);
    }

    /**
     * 使用分布式会话时在执行结束时保存数据
     */
    public void touch() {
        if (_ctx != null && _engineConfiguration.getCacheManager().isCluster())
            _session.setAttribute(_ctx.getId(), _ctx);
    }

    /**
     * 添加要执行的任务
     *
     * @param taskId
     * @param code
     */
    public void appendTask(String taskId, String code) {
        if (_executeTasks == null)
            _executeTasks = new ArrayList<>();
        CommandContext cc = new CommandContext();
        cc._user = this._user.clone();
        cc._env = this._env.clone();
        cc._ctx = _ctx.clone();
        cc._projectService = this._projectService;
        cc._engineConfiguration = this._engineConfiguration;
        cc._user.setDeviceType(DeviceType.JOB);
        cc._env.setDeviceType(DeviceType.JOB);
        ExecuteContext ec = new ExecuteContext(cc, taskId, code);
        _executeTasks.add(ec);
    }

    /**
     * 开始批量执行多个任务
     */
    public void executeBatch(boolean await) {
        if (_executeTasks == null || _executeTasks.isEmpty())
            return;
        _engineConfiguration.getTaskExecuter(this).executeBatch(_executeTasks, await);
    }

    /**
     * 重新设置会话，使用在并发处理功能的时候
     *
     * @param session
     */
    public void setSession(Session session) {
        this._session = session;
        if (_ctx != null)//在新的session注册来源功能信息
            _session.setAttribute(_ctx.getId(), _ctx);
    }

    /**
     * 待执行的功能
     */
    private List<ExecuteContext> _executeTasks;
}
